#include "types.h"
#include "biscuit.h"
#include "params.h"
#include "memory.h"
#include "util.h"
#include "kvm.h"
#include "kalloc.h"
#include "lock.h"
#include "kobj.h"
#include "kfunc.h"
#include "proc.h"

extern char IDLE[];
extern char INITCODE[];
extern void * kpgtb;
extern char trampoline[]; 
extern char uservec[];
extern char userret[];
extern void * kstack[NR_PROC];
extern void * ktrapframe[NR_PROC];
extern struct ko* consko;

void usertrap ();

void allocmempgtb (struct proc* p, int risk);

struct {
    struct proc p[NR_PROC];
    int map [NR_PROC];
    struct proc * run;
    struct sleeplk lk;
} procs;


/* 开中断 */
void
intr_on() {
    int mstatus = ll_r_mstatus ();
    set_mstatus_mie (mstatus, 1);
    ll_w_mstatus (mstatus);
}

void
intr_off () {
    int mstatus = ll_r_mstatus ();
    clr_mstatus_mie (mstatus);
    ll_w_mstatus (mstatus);
}

void
forkret () {
    struct proc * p;
    //panic ("forkret");
    p = myproc ();
    p->tf->a0 = p->pid;
    intr_off ();
    int status;
    ll_w_mtvec ((void*)TRAMPOLINE + (uservec-trampoline));
    status = ll_r_mstatus ();
    set_mstatus_mpie (status, 1);
    ll_w_mstatus (status);
    // ll_w_mtvec (usertrap);
    ll_w_mepc ((void*)p->mepc);
    //panic ("forkret");
    seeyou (ktrapframe[p->pid], p->pgtb);
}

/* NOTE: 这个过程天然是原子的 */
int
fork () {
    struct proc * p, *newp;
    p = myproc ();
    newp = allocproc (0);
    newp->mepc = p->mepc;
    copyctx (p, newp); // 拷贝内核上下文
    copyfdtab (p, newp); // 拷贝打开文件表
    copymem (p, newp); // 拷贝内存
    copytrapframe (p, newp); // 拷贝trapframe
    newp->ctx.ra = forkret;
    newp->parent = p;
    p->childs[newp->pid] = newp;
    newp->stat = PROC_RUNNABLE;
    return 0;
}

int
riskfork () {
    struct proc * p, *newp;
    p = myproc ();
    newp = allocproc (1);
    copyctx (p, newp); // 拷贝内核上下文
    copyfdtab (p, newp); // 拷贝打开文件表
    copymem (p, newp); // 拷贝内存
    copytrapframe (p, newp); // 拷贝trapframe
    newp->ctx.ra = forkret;
    newp->parent = p;
    p->childs[newp->pid] = newp;
    newp->stat = PROC_RUNNABLE;
    return 0;
}


int
exec (char * name, char * arg) {
    struct proc * p;
    unsigned int entry;
    // panic ("exec");
    p = myproc ();
    // panic ("loadelf");
    entry = loadelf (name, p->pgtb);
    bzero (p->tf, sizeof (struct trapframe));
    if ((int)entry == -1)
        return -1;
    p->mepc = (void*)entry;
    strncpy(p->tf->arg, arg, MAX_NAMESZ);
    #define TFARG_OFF 136
    p->tf->a0 = TRAPFRAME + TFARG_OFF; // a0指向trapframe->arg
    #undef TFARG_OFF
    return 0;
}

void
exit () {
    yield ();
}

void
sleep (void * wh) {
    struct proc * p;
    p = myproc ();
    p->sleepon = wh;
    p->stat = PROC_SLEEP;
    //panic ("sleep");
    sched ();
}


void
wakeup (void * wh) {
    struct proc * p;
    for (p = procs.p; p < procs.p + NR_PROC; p++) {
        if ((p->stat == PROC_SLEEP || p->stat == PROC_WAITING) &&
                                                p->sleepon == wh) {
            p->sleepon = 0;
            p->stat = PROC_RUNNABLE;
        }
    }
}

void
yield () {
    again:
    struct proc * p;
    p = myproc ();
    sleep_lock (&p->parent->lk);
    if (p->parent->stat == PROC_WAITING &&
                p->parent->scratch == 0) {
        p->parent->scratch = p;
        sleep_unlock (&p->parent->waitlk);
        p->stat = PROC_DEAD;
        goto sw;
    }
    p->stat = PROC_ZOMBIE;
    sleep_unlock (&p->parent->lk);
    sw:
    sched ();
    goto again;
}

int
wait () {
    struct proc * p;
    void * scratch;
    int chpid;
    p = myproc ();
    p->stat = PROC_WAITING;
    sleep_lock (&p->waitlk);
    scratch = p->scratch;
    chpid = (struct proc *)scratch - procs.p;
    p->childs[chpid] = 0;
    p->scratch = 0;
    return  chpid;
}

void
procinit () {
    bzero (&procs, sizeof (procs));
}

struct proc *
myproc () {
    return procs.run;
}

void
sched () { // 一定会选择出一个可运行进程.系统内存在一个idle进程
    intr_off ();
    struct proc * cur, *nxt;
    cur = myproc ();
    int i, j;
    //again:
    for (i = 0, j = cur - procs.p; i < NR_PROC; i++, j++) {
        nxt = procs.p + ((j+1) % NR_PROC);
        if (nxt->stat == PROC_ZOMBIE) {
            if (nxt->parent->stat == PROC_WAITING &&
                         nxt->parent->scratch == 0) {
                nxt->stat = PROC_RUNNABLE;  // HP++
            }
            if (nxt->parent->stat == PROC_DEAD || 
                nxt->parent->stat == PROC_UNUSED) { // parent dead, reparent to neo
                #define neo (procs.p[0])
                sleep_lock (&neo.lk);
                    neo.childs[nxt->pid] = nxt;
                    nxt->parent = &neo;
                sleep_unlock (&neo.lk);
                nxt->stat = PROC_RUNNABLE;
                #undef neo
            }
        }
        if (nxt->stat == PROC_DEAD) {
            freeproc (nxt);
        }
        if (nxt->stat == PROC_RUNNABLE) {
            goto found;
        }
    }
    if (nxt->stat != PROC_RUNNABLE && // should never happen
        nxt->stat != PROC_RUNNING)
        panic ("sched");
    // 结束循环时nxt = cur
    found:
    if(cur->stat == PROC_RUNNING)
        cur->stat = PROC_RUNNABLE;
    nxt->stat = PROC_RUNNING;
    procs.run = nxt;
    printk ("sched selecting %d\n", nxt->pid);
    printk ("ctx.ra:%p, sp:%p\n", nxt->ctx.ra, nxt->ctx.sp);
    // panic ("my sched");
    portal (&cur->ctx, &nxt->ctx); // 切换ra,内核栈
    intr_on ();
}

// 由neo执行exec("init");
// char
// initcode [] = {
// 0x0
// };

void
creat_idle () {
    printk ("creating idle\n");
    struct proc * idle = allocproc (0);
    idle->tf->sp = (void*)USTACK + PGSZ; 
    idle->tf->utraphdlr = usertrap;
    //printk ("proc allocated.\n");
    vmwrite (idle->pgtb, IDLE, 0x0, PGSZ);
    idle->mepc = 0x0;
    idle->ctx.ra = forkret;
    idle->ctx.sp = idle->tf->ksp;
    //int status;
    //status = ll_r_mstatus ();
    // set_mstatus_mpp (status, 0); // usermode
    // set_mstatus_mpie (status, 1); // intr on
    // ll_w_mstatus (status);
    idle->stat = PROC_RUNNABLE;
    //lockinit (); // 初始化锁
    //panic ("initneo");
    // seeyou ((void*)TRAPFRAME, idle->pgtb);  // note:USERRET应为
    printk ("idle created\n");
    //return 0;
}


void
initneo () {
    printk ("creating init\n");
    struct proc * neo = allocproc (0);
    creat_idle ();
    procs.run = neo;
    consko = allocko ();
    //vmwrite (neo->pgtb, initcode, 0x0, sizeof(initcode));
    neo->tf->sp = (void*)USTACK + PGSZ; 
    neo->tf->utraphdlr = usertrap;
    //printk ("proc allocated.\n");
    vmwrite (neo->pgtb, INITCODE, 0x0, PGSZ);
    neo->mepc = 0x0;
    int status;
    status = ll_r_mstatus ();
    set_mstatus_mpp (status, 0); // usermode
    set_mstatus_mpie (status, 1); // intr on
    ll_w_mstatus (status);
    neo->stat = PROC_RUNNING;
    lockinit (); // 初始化锁
    creat_consko (consko); // STDIN
    dup (0); // STDOUT
    elfloaderinit ();
    printk ("executing initcode, jumping to userspace.\n");
    unsigned int time;
    time = ll_r_mtime();
    time += 100000;
    ll_w_mtimecmp (time);
    seeyou ((void*)TRAPFRAME, neo->pgtb);  
}

struct proc *
allocproc (int risk) {
    struct proc *p;
    //void * newpg;
    unsigned int i;
    sleep_lock (&procs.lk);
    for (i = 0; i < NR_PROC; i++) {
        if (procs.map[i] == 0) {
            procs.map[i] = 1;
            p = &procs.p[i];
            bzero (p, sizeof (struct proc));
            goto found;
        }
    }
    sleep_unlock (&procs.lk);
    return 0;
    found:
    sleep_unlock (&procs.lk);
    p->pid = i;
    printk ("allocating pid:%d\n",i);
    p->stat = PROC_PROCESSING;
    p->tf = ktrapframe[i];
    bzero (p->tf, sizeof (struct trapframe));
    p->tf->ksp = kstack[i]+PGSZ; // sp指向最高地址
    printk ("pid:%d kernel stack pointer:%p\n", i ,p->tf->ksp);
    p->tf->kpgtb = kpgtb; // 内核页表
    printk ("allocating memory\n");
    p->tf->utraphdlr = usertrap;
    //printk ("trapframe:%p\n",ktrapframe[p->pid]);
    //ll_halt ();
    allocmempgtb (p, risk); // 分配页表,内存
    // 映射trapframe, 运行参数在trapframe->arg;
    kvmmap ((void*)TRAPFRAME, ktrapframe[p->pid], p->pgtb, PTE_U | PTE_R | PTE_W,p);
    // 映射trampoline
    kvmmap ((void*)TRAMPOLINE, trampoline, p->pgtb, PTE_R | PTE_W | PTE_X,p);
    sleep_lock(&p->waitlk); 
    return p;
}

void
allocmempgtb (struct proc* p, int risk) {
    void * newpg;
    int i;
    newpg = kalloc (); // 页表
    p->pgtb = newpg;
    // 8M连续内存
    pushlinkpg (p->allocpgs, ((struct linkpgs*)newpg));
    for (i = 0; i < NR_PROCPGS; i++) {
        newpg = kalloc ();
        kvmmap ((void*)(i*PGSZ), newpg, p->pgtb, PTE_U | PTE_R | PTE_W | PTE_X,p);
        pushlinkpg (p->allocpgs, ((struct linkpgs*)newpg));
        //printk ("allocating page 0x%x\n", newpg);
    }
    // 分配用户栈
    newpg = kalloc (); // 用户栈
    pushlinkpg (p->allocpgs, ((struct linkpgs*)newpg));
    // 映射用户栈
    if (risk)
        kvmmap ((void*)USTACK, newpg, p->pgtb, PTE_U | PTE_R | PTE_W | PTE_X,p);
    else
        kvmmap ((void*)USTACK, newpg, p->pgtb, PTE_U | PTE_R | PTE_W,p);
}

void
freeallpgs (struct proc * p) {
    struct linkpgs *pg; // Q:页面回收后当前栈还能正常使用吗?A:不影响正常使用
    while (poplinkpg(p->allocpgs, pg)) {
        kfree (pg);
    }
}

int
copymem (struct proc * from, struct proc * to) {
    void * kbuf;
    int i;
    kbuf = kalloc ();
    for (i = 0; i < NR_PROCPGS; i++) { // 拷贝内存
        vmread (from->pgtb, i*PGSZ, kbuf, PGSZ);
        vmwrite (to->pgtb, kbuf, i*PGSZ, PGSZ);
    }
    vmread (from->pgtb, USTACK, kbuf, PGSZ);
    vmwrite (to->pgtb, kbuf, USTACK, PGSZ);
    kfree (kbuf);
    return 0;
}

int
copytrapframe (struct proc * from, struct proc * to) {
    memcpy (to->tf, from->tf, sizeof (struct trapframe));
    return 0;
}

int
copyctx (struct proc * from, struct proc * to) {
    memcpy (&to->ctx, &from->ctx, sizeof (struct proctx));
    // memcpy (kstack[to->pid], kstack[from->pid], PGSZ); // 分离内核栈
    to->ctx.sp = kstack[to->pid] + PGSZ; // 分离内核栈,调用portal时子进程会跳转到forkret
    return 0;
}

int
copyfdtab (struct proc * from, struct proc * to) {
    int i;
    for (i = 0; i < NR_MAXOPEN; i++) {
        if (from->fdtab[i]) {
            from->fdtab[i]->refs++;
            to->fdtab[i] = from->fdtab[i];
        }
    }
    return 0;
}

// 负责回收进程及其页面
int
freeproc (struct proc * p) {
    if (procs.map[p->pid] == 0)
        goto bad;
    freeallpgs (p);
    p->stat = PROC_UNUSED;
    procs.map[p->pid] = 0;
    return 0;
    bad:
    return -1;
}